
#include "_public.h"
#include "_ooci.h"

CLogFile logfile;	  // 服务程序的运行日志。
CTcpServer TcpServer; // 创建服务端对象。

struct st_arg
{
	char connstr[101]; // 数据库的连接参数
	char charset[51];  // 数据库的字符集
	int port;		   // web服务监听的端口
} starg;

void EXIT(int sig); // 进程的退出函数。

pthread_spinlock_t vthidlock; // 用于锁定vthid的自旋锁。
vector<pthread_t> vthid;	  // 存放全部线程id的容器。
void *thmain(void *arg);	  // 线程主函数。

void thcleanup(void *arg); // 线程清理函数。

// 显示程序的帮助
void _help(char *argv[]);

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer);

int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		printf("Using:./demo20 port logfile\nExample:./demo20 5005 /tmp/demo20.log\n\n");
		return -1;
	}

	// 关闭全部的信号和输入输出。
	// 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
	// 但请不要用 "kill -9 +进程号" 强行终止
	CloseIOAndSignal();
	signal(SIGINT, EXIT);
	signal(SIGTERM, EXIT);

	if (logfile.Open(argv[2], "a+") == false)
	{
		printf("logfile.Open(%s) failed.\n", argv[2]);
		return -1;
	}

	// 服务端初始化。
	if (TcpServer.InitServer(atoi(argv[1])) == false)
	{
		logfile.Write("TcpServer.InitServer(%s) failed.\n", argv[1]);
		return -1;
	}

	pthread_spin_init(&vthidlock, 0);

	while (true)
	{
		// 等待客户端的连接请求。
		if (TcpServer.Accept() == false)
		{
			logfile.Write("TcpServer.Accept() failed.\n");
			EXIT(-1);
		}

		logfile.Write("客户端（%s）已连接。\n", TcpServer.GetIP());

		// 创建一个新的线程，让它与客户端通讯。
		pthread_t thid;
		if (pthread_create(&thid, NULL, thmain, (void *)(long)TcpServer.m_connfd) != 0)
		{
			logfile.Write("pthread_create() failed.\n");
			TcpServer.CloseListen();
			continue;
		}

		pthread_spin_lock(&vthidlock);
		vthid.push_back(thid); // 把线程id放入容器。
		pthread_spin_unlock(&vthidlock);
	}
}

void *thmain(void *arg) // 线程主函数。
{
	pthread_cleanup_push(thcleanup, arg); // 把线程清理函数入栈（关闭客户端的socket）。

	int connfd = (int)(long)arg; // 客户端的socket。

	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); // 线程取消方式为立即取消。

	pthread_detach(pthread_self()); // 把线程分离出去。

	// 子进程与客户端进行通讯，处理业务。
	int ibuflen;
	char buffer[102400];

	// 与客户端通讯，接收客户端发过来的报文后，回复ok。
	while (1)
	{
		memset(buffer, 0, sizeof(buffer));
		if (TcpRead(connfd, buffer, &ibuflen, 30) == false)
			break; // 接收客户端的请求报文。
		logfile.Write("接收：%s\n", buffer);

		strcpy(buffer, "ok");
		if (TcpWrite(connfd, buffer) == false)
			break; // 向客户端发送响应结果。
		logfile.Write("发送：%s\n", buffer);
	}

	close(connfd); // 关闭客户端的连接。

	// 把本线程id从存放线程id的容器中删除。
	pthread_spin_lock(&vthidlock);
	for (int ii = 0; ii < vthid.size(); ii++)
	{
		if (pthread_equal(pthread_self(), vthid[ii]))
		{
			vthid.erase(vthid.begin() + ii);
			break;
		}
	}
	pthread_spin_unlock(&vthidlock);

	pthread_cleanup_pop(1); // 把线程清理函数出栈。
}

// 进程的退出函数。
void EXIT(int sig)
{
	// 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
	signal(SIGINT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);

	logfile.Write("进程退出，sig=%d。\n", sig);

	TcpServer.CloseListen(); // 关闭监听的socket。

	// 取消全部的线程。
	for (int ii = 0; ii < vthid.size(); ii++)
	{
		pthread_cancel(vthid[ii]);
	}

	sleep(1); // 让子线程有足够的时间退出。

	pthread_spin_destroy(&vthidlock);

	exit(0);
}

void thcleanup(void *arg) // 线程清理函数。
{
	close((int)(long)arg); // 关闭客户端的socket。

	logfile.Write("线程%lu退出。\n", pthread_self());
}

// 显示程序的帮助
void _help(char *argv[])
{
	printf("Using:/project/tools/bin/webserver1 logfilename xmlbuffer\n\n");

	printf("Sample:/project/tools/bin/procctl 10 /project/tools/bin/webserver1 /log/idc/webserver1.log \"<connstr>127.0.0.1,root,mysqlpwd,mysql,3306</connstr><charset>utf8</charset><port>5008</port><timeout>5</timeout>\"\n\n");

	printf("本程序是数据总线的服务端程序，为数据中心提供http协议的数据访问接口。\n");
	printf("logfilename 本程序运行的日志文件。\n");
	printf("xmlbuffer   本程序运行的参数，用xml表示，具体如下：\n\n");

	printf("connstr     数据库的连接参数，格式：ip,username,password,dbname,port。\n");
	printf("charset     数据库的字符集，这个参数要与数据源数据库保持一致，否则会出现中文乱码的情况。\n");
	printf("port        web服务监听的端口。\n");
	printf("timeout     线程等待客户端报文的超时时间，单位：秒，建议取值小于10。\n\n");
}

// 把xml解析到参数starg结构中
bool _xmltoarg(char *strxmlbuffer)
{
	memset(&starg, 0, sizeof(struct st_arg));

	GetXMLBuffer(strxmlbuffer, "connstr", starg.connstr, 100);
	if (strlen(starg.connstr) == 0)
	{
		logfile.Write("connstr is null.\n");
		return false;
	}

	GetXMLBuffer(strxmlbuffer, "charset", starg.charset, 50);
	if (strlen(starg.charset) == 0)
	{
		logfile.Write("charset is null.\n");
		return false;
	}

	GetXMLBuffer(strxmlbuffer, "port", &starg.port);
	if (starg.port == 0)
	{
		logfile.Write("port is null.\n");
		return false;
	}

	GetXMLBuffer(strxmlbuffer, "timeout", &starg.timeout);
	if (starg.timeout == 0)
	{
		logfile.Write("timeout is null.\n");
		return false;
	}

	// 强制timeout不能超过20秒，否则会引起线程超时。
	if (starg.timeout > 20)
		starg.timeout = 20;

	return true;
}
